home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacGames Sampler
/
PHT MacGames Bundle.iso
/
MacSource Folder
/
Samples from the CD
/
C and C++
/
MIDI lib
/
MIDI.Txt
< prev
next >
Wrap
Text File
|
1994-04-20
|
28KB
|
861 lines
; Low Level MIDI routines with time-stamping
; Written by Kirk Austin 5/17/87
; This code is in the public domain and is absolutely free
; Note: Be sure and turn off range checking in LS Pascal
; to prevent a crash.
; Serial Chip equates
SCCRd EQU $1D8
SCCWr EQU $1DC
aData EQU 6
aCtl EQU 2
bData EQU 4
bCtl EQU 0
TBE EQU 2
; Interrupt vector equates
Lvl1DT EQU $192
Lvl2DT EQU $1B2
RxIntOffsetA EQU 24
TxIntOffsetA EQU 16
SpecRecCondA EQU 28
RxIntOffsetB EQU 8
TxIntOffsetB EQU 0
SpecRecCondB EQU 12
; 6522 equates
VIA EQU $1D4
vT1C EQU $800
vT1CH EQU $A00
vT1L EQU $C00
vACR EQU $1600
vIER EQU $1C00
; XDEF all routines that need to be accessed externally
XDEF InitSCCA
XDEF InitSCCB
XDEF TxMIDIA
XDEF TxMIDIB
XDEF RxMIDIA
XDEF RxMIDIB
XDEF ResetSCCA
XDEF ResetSCCB
XDEF InitTimer
XDEF LoadTimer
XDEF StartCounter
XDEF GetCounter
XDEF QuitTimer
; These are the routines for the Modem Port
; PROCEDURE InitSCCA;
; Call this routine at the beginning of your application if
; using the modem port for MIDI information transfers.
InitSCCA
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A2,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
MOVE.L SCCRd,A1 ; Get base Read address
ADD #aCtl,A1 ; Add offset for control
MOVE.B (A1),D0 ; Dummy read
MOVE.L (SP),(SP) ; Delay
MOVE.L SCCWr,A0 ; Get base Write address
ADD #aCtl,A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%10000000,(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
BSR InitSCCChan ; branch to common init routine
; set up interrupt vectors
MOVE.L #Lvl2DT,A0 ; get dispatch table ptr
MOVE #RxIntOffsetA,D0 ; get offset to Rx vector
LEA PRxIntHandA,A1 ; point to previous vector storage
MOVE.L 0(A0,D0),(A1) ; save previous interrupt vector
LEA RxIntHandA,A1 ; set Rx vector
MOVE.L A1,0(A0,D0)
MOVE #TxIntOffsetA,D0 ; get offset to Tx vector
LEA PTxIntHandA,A1 ; point to previous vector storage
MOVE.L 0(A0,D0),A1 ; save previous interrupt vector
LEA TxIntHandA,A1 ; set Tx vector
MOVE.L A1,0(A0,D0)
MOVE #SpecRecCondA,D0 ; offset to Special vector
LEA StubA,A1
MOVE.L A1,0(A0,D0)
; initialize the flags & pointers
LEA RxByteInA,A2 ; get the address
CLR (A2)
LEA RxByteOutA,A2 ; get the address
CLR (A2)
LEA RxQEmptyA,A2 ; get the address
MOVE #$FFFF,(A2)
LEA TxByteInA,A2 ; get the address
CLR (A2)
LEA TxQEmptyA,A2 ; get the address
MOVE #$FFFF,(A2)
MOVEM.L (SP)+,D0/A0-A2 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
RTS ; and return
; This is the common initialization routine for both channels
InitSCCChan
MOVE.B #4,(A0) ; pointer for SCC reg 4
MOVE.L (SP),(SP) ; Delay
MOVE.B #%10000100,(A0) ; 32x clock, 1 stop bit
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; No W/Req
MOVE.L (SP),(SP) ; Delay
MOVE.B #3,(A0) ; pointer for SCC reg 3
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Turn off Rx
MOVE.L (SP),(SP) ; Delay
MOVE.B #5,(A0) ; pointer for SCC reg 5
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Turn off Tx
MOVE.L (SP),(SP) ; Delay
MOVE.B #11,(A0) ; pointer for SCC reg 11
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00101000,(A0) ; Make TRxC clock source
MOVE.L (SP),(SP) ; Delay
MOVE.B #14,(A0) ; pointer for SCC reg 14
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Disable BRGen
MOVE.L (SP),(SP) ; Delay
MOVE.B #3,(A0) ; pointer for SCC reg 3
MOVE.L (SP),(SP) ; Delay
MOVE.B #%11000001,(A0) ; Enable Rx
MOVE.L (SP),(SP) ; Delay
MOVE.B #5,(A0) ; pointer for SCC reg 5
MOVE.L (SP),(SP) ; Delay
MOVE.B #%01101010,(A0) ; Enable Tx and drivers
MOVE.L (SP),(SP) ; Delay
MOVE.B #15,(A0) ; pointer for SCC reg 15
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001000,(A0) ; Enable DCD int for mouse
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010011,(A0) ; Enable interrupts
MOVE.L (SP),(SP) ; Delay
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001010,(A0) ; Set master int enable
MOVE.L (SP),(SP) ; Delay
RTS
; PROCEDURE TxMIDIA (TheData : integer);
; This is the routine to transmit a MIDI byte of data
; through the Modem Port. To use this routine place
; the byte to be transmitted as the lower 8 bits
; of a word on the stack, then call TxMIDIA.
TxMIDIA
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A3,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
LEA TxQEmptyA,A3 ; get the address
TST.B (A3) ; is TxQueue empty?
BNE TxQEA ; if so branch
LEA TxByteInA,A3 ; get the address
MOVE (A3),D0 ; if not add byte to queue
LEA TxQueueA,A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update TxByteIn
CMP #$100,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,(A3)
BRA TxExitA ; and exit
TxQEA
MOVE.L SCCRd,A0 ; get SCC Read Address
MOVE.L SCCWr,A1 ; get SCC Write Address
MOVE #aCtl,D0 ; get index for Ctl
BTST.B #TBE,0(A0,D0) ; transmit buffer empty?
BNE FirstByteA ; if so branch
LEA TxByteInA,A3 ; get the address
MOVE (A3),D0 ; if not add to queue
LEA TxQueueA,A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update pointer
CMP #$100,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,(A3)
LEA TxQEmptyA,A3 ; get the address
MOVE #0,(A3) ; reset queue empty flag
BRA TxExitA ; and exit
FirstByteA
MOVE #aData,D0 ; get index to data
MOVE.L (SP),(SP) ; Delay
MOVE.B 9(A6),0(A1,D0) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
TxExitA
MOVEM.L (SP)+,D0/A0-A3 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
UNLK A6 ; release frame pointer
MOVE.L (SP)+,A1 ; save return address
ADD.L #2,SP ; move past data word
MOVE.L A1,-(SP) ; put address back on stack
RTS ; and return
; Function RxMIDIA : LongInt;
; This routine gets a byte through the modem port.
; To use this routine treat it like a Pascal
; function. Leave space on the stack for a longword
; of data before calling this routine. If the data
; on the stack after
; the routine executes is 0 there was no MIDI data available.
; If it's non-0 the upper 3 bytes contain the counter
; value, the MIDI byte is the low byte.
RxMIDIA
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; save interrupts
MOVEM.L D0-D1/A0-A3,-(SP) ; Save registers
ORI #$0300,SR ; disable interrupts
LEA RxQEmptyA,A3 ; get the address
TST.B (A3) ; any data available?
BEQ @1 ; if so, branch
MOVE.L #0,8(A6) ; if not, return with 0
BRA RxExitA
@1 LEA RxByteOutA,A3 ; get the address
MOVE (A3),D0 ; get index to byte out
LEA RxQueueA,A2 ; point to queue
MOVE.L #0,D1 ; clear data register
MOVE.L 0(A2,D0),D1 ; get MIDI data
MOVE.L D1,8(A6) ; place on stack for return
ADDQ #4,D0 ; update index
CMP #$400,D0
BNE @2
MOVE #0,D0
@2 LEA RxByteOutA,A3 ; get the address
MOVE D0,(A3)
LEA RxByteInA,A3 ; get the address
MOVE (A3),D1
CMP D0,D1 ; is queue empty?
BNE RxExitA ; if not exit
LEA RxQEmptyA,A3 ; get the address
MOVE #$FFFF,(A3) ; if empty, set flag
RxExitA
MOVEM.L (SP)+,D0-D1/A0-A3 ; Restore registers
MOVE (SP)+,SR ; restore interrupts
UNLK A6
RTS ; and return
; This is the interrupt routine for receiving through
; the modem port. It places the counter value and the
; MIDI byte in a circular queue to be
; accessed later by the application.
; When the system gets this far, A0 contains the
; SCC base read Ctl address
; and A1 contains the SCC base write Ctl address
; for this channel. The data addresses are offset by 4
; from the control addresses.
; D0-D3/A0-A3 are already preserved, so they may
; be used freely.
RxIntHandA
ORI #$0300,SR ; disable interrupts
@3 MOVE #4,D0 ; get data offset
CLR.L D1 ; prepare for data
MOVE.L (SP),(SP) ; Delay
MOVE.B 0(A0,D0),D1 ; read data from SCC
MOVE.L (SP),(SP) ; Delay
LEA RxQueueA,A2 ; point to queue
LEA RxByteInA,A3 ; get the address
MOVE (A3),D0 ; get offset to next cell
LEA Counter,A3 ; get the address
MOVE.L (A3),D2 ; put counter value in D2
LSL.L #8,D2 ; shift counter one byte
ADD.L D2,D1 ; combine counter and data
MOVE.L D1,0(A2,D0) ; put longword in queue
LEA RxQEmptyA,A3 ; get the address
MOVE #0,(A3) ; reset queue empty flag
ADDQ #4,D0 ; update index
CMP #$400,D0
BNE @1
MOVE #0,D0
@1 LEA RxByteInA,A3 ; get the address
MOVE D0,(A3)
@2 BTST.B #0,(A0) ; is there more data?
BNE @3 ; do it again if there is
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; This is the interrupt routine for transmitting a byte
; through the modem port. It checks to see if there
; is any data to send, and if there is it sends it to
; the SCC. If there isn't it resets the TBE interrupt
; in the SCC and exits.
; When the system gets this far, A0 contains the SCC
; base read Ctl address and A1 contains the SCC base
; write Ctl address for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so
; they may be used freely.
TxIntHandA
ORI #$0300,SR ; disable interrupts
LEA TxQEmptyA,A3 ; get the address
TST.B (A3) ; Is queue empty?
BEQ @1 ; if not branch
MOVE.B #$28,(A1) ; if so, reset TBE interrupt
MOVE.L (SP),(SP) ; Delay
BRA TxIExitA ; and exit
@1 LEA TxByteOutA,A3 ; get the address
MOVE (A3),D0 ; get index to next data byte
LEA TxQueueA,A2 ; point to queue
MOVE #4,D1 ; get data offset
MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
ADDQ #1,D0 ; update index
CMP #$100,D0
BNE @2
MOVE #0,D0
@2 LEA TxByteOutA,A3 ; get the address
MOVE D0,(A3)
LEA TxByteInA,A3 ; get the address
MOVE (A3),D1
CMP D0,D1 ; is TxQueue empty?
BNE TxIExitA ; if not exit
LEA TxQEmptyA,A3 ; get the address
MOVE #$FFFF,(A3) ; if empty set flag
TxIExitA
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; PROCEDURE ResetSCCA;
; If you called InitSCCA at the beginning of your
; application this
; routine must be called when the application
; quits or the system will
; crash due to the interrupt handling pointers
; becoming invalid.
ResetSCCA
MOVEM.L D0/A0-A1,-(SP) ; save registers
MOVE SR,-(SP) ; Save interrupts
ORI #$0300,SR ; Disable interrupts
MOVE.L SCCWr,A0 ; Get base Write address
ADD #aCtl,A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%10000000,(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
BSR ResetSCCChan ; branch to common reset routine
MOVE.L #Lvl2DT,A0 ; get dispatch table pointer
MOVE #RxIntOffsetA,D0 ; get offset to Rx vector
LEA PRxIntHandA,A1 ; point to previous vector storage
MOVE.L (A1),0(A0,D0) ; restore previous int vector
MOVE #TxIntOffsetA,D0 ; get offset to Tx vector
LEA PTxIntHandA,A1 ; set Rx vector
MOVE.L (A1),0(A0,D0) ; restore previous int vector
MOVE (SP)+,SR ; Restore interrupts
MOVEM.L (SP)+,D0/A0-A1 ; restore registers
RTS ; and return
; This is the common reset routine for both channels
ResetSCCChan
MOVE.B #15,(A0) ; pointer for SCC reg 15
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001000,(A0) ; Enable DCD int
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000001,(A0) ; Enable mouse interrupts
MOVE.L (SP),(SP) ; Delay
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001010,(A0) ; Set master int enable
MOVE.L (SP),(SP) ; Delay
RTS
TxQueueA DCB.B $100,0 ; this is the queue
TxQEmptyA DC 0 ; the queue empty flag
TxByteInA DC 0 ; index to next cell in
TxByteOutA DC 0 ; index to next cell out
RxQueueA DCB.B $400,0 ; this is the queue
RxQEmptyA DC 0 ; the empty queue flag
RxByteInA DC 0 ; index to next cell in
RxByteOutA DC 0 ; index to next cell out
PRxIntHandA DC 0 ; Previous interrupt vector
PTxIntHandA DC 0 ; Previous interrupt vector
; These are the routines for the Printer Port
; PROCEDURE InitSCCB;
; Call this routine at the beginning of your application if
; using the printer port for MIDI information transfers.
InitSCCB
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A2,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
MOVE.L SCCRd,A1 ; Get base Read address
ADD #bCtl,A1 ; Add offset for control
MOVE.B (A1),D0 ; Dummy read
MOVE.L (SP),(SP) ; Delay
MOVE.L SCCWr,A0 ; Get base Write address
ADD #bCtl,A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%01000000,(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
BSR InitSCCChan ; branch to common init routine
; set up interrupt vectors
MOVE.L #Lvl2DT,A0 ; get dispatch table ptr
MOVE #RxIntOffsetB,D0 ; get offset to Rx vector
LEA PRxIntHandB,A1 ; point to previous vector storage
MOVE.L 0(A0,D0),(A1) ; save previous interrupt vector
LEA RxIntHandB,A1 ; set Rx vector
MOVE.L A1,0(A0,D0)
MOVE #TxIntOffsetB,D0 ; get offset to Tx vector
LEA PTxIntHandB,A1 ; point to previous vector storage
MOVE.L 0(A0,D0),A1 ; save previous interrupt vector
LEA TxIntHandB,A1 ; set Tx vector
MOVE.L A1,0(A0,D0)
MOVE #SpecRecCondB,D0 ; offset to Special vector
LEA StubB,A1
MOVE.L A1,0(A0,D0)
; initialize the flags & pointers
LEA RxByteInB,A2 ; get the address
CLR (A2)
LEA RxByteOutB,A2 ; get the address
CLR (A2)
LEA RxQEmptyB,A2 ; get the address
MOVE #$FFFF,(A2)
LEA TxByteInB,A2 ; get the address
CLR (A2)
LEA TxQEmptyB,A2 ; get the address
MOVE #$FFFF,(A2)
MOVEM.L (SP)+,D0/A0-A2 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
RTS ; and return
; PROCEDURE TxMIDIB (TheData : integer);
; This is the routine to transmit a MIDI byte of data
; through the Printer Port. To use this routine place
; the byte to be transmitted as the lower 8 bits
; of a word on the stack, then call TxMIDIB.
TxMIDIB
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A3,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
LEA TxQEmptyB,A3 ; get the address
TST.B (A3) ; is TxQueue empty?
BNE TxQEB ; if so branch
LEA TxByteInB,A3 ; get the address
MOVE (A3),D0 ; if not add byte to queue
LEA TxQueueB,A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update TxByteIn
CMP #$100,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,(A3)
BRA TxExitB ; and exit
TxQEB
MOVE.L SCCRd,A0 ; get SCC Read Address
MOVE.L SCCWr,A1 ; get SCC Write Address
MOVE #bCtl,D0 ; get index for Ctl
BTST.B #TBE,0(A0,D0) ; transmit buffer empty?
BNE FirstByteB ; if so branch
LEA TxByteInB,A3 ; get the address
MOVE (A3),D0 ; if not add to queue
LEA TxQueueB,A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update pointer
CMP #$100,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,(A3)
LEA TxQEmptyB,A3 ; get the address
MOVE #0,(A3) ; reset queue empty flag
BRA TxExitB ; and exit
FirstByteB
MOVE #bData,D0 ; get index to data
MOVE.L (SP),(SP) ; Delay
MOVE.B 9(A6),0(A1,D0) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
TxExitB
MOVEM.L (SP)+,D0/A0-A3 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
UNLK A6 ; release frame pointer
MOVE.L (SP)+,A1 ; save return address
ADD.L #2,SP ; move past data word
MOVE.L A1,-(SP) ; put address back on stack
RTS ; and return
; Function RxMIDIB : LongInt;
; This routine gets a byte through the printer port.
; To use this routine treat it like a Pascal
; function. Leave space on the stack for a longword
; of data before calling this routine. If the data
; on the stack after
; the routine executes is 0 there was no MIDI data available.
; If it's non-0 the upper 3 bytes contain the counter
; value, the MIDI byte is the low byte.
RxMIDIB
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; save interrupts
MOVEM.L D0-D1/A0-A3,-(SP) ; Save registers
ORI #$0300,SR ; disable interrupts
LEA RxQEmptyB,A3 ; get the address
TST.B (A3) ; any data available?
BEQ @1 ; if so, branch
MOVE.L #0,8(A6) ; if not, return with 0
BRA RxExitB
@1 LEA RxByteOutB,A3 ; get the address
MOVE (A3),D0 ; get index to byte out
LEA RxQueueB,A2 ; point to queue
MOVE.L #0,D1 ; clear data register
MOVE.L 0(A2,D0),D1 ; get MIDI data
MOVE.L D1,8(A6) ; place on stack for return
ADDQ #4,D0 ; update index
CMP #$400,D0
BNE @2
MOVE #0,D0
@2 LEA RxByteOutB,A3 ; get the address
MOVE D0,(A3)
LEA RxByteInB,A3 ; get the address
MOVE (A3),D1
CMP D0,D1 ; is queue empty?
BNE RxExitB ; if not exit
LEA RxQEmptyB,A3 ; get the address
MOVE #$FFFF,(A3) ; if empty, set flag
RxExitB
MOVEM.L (SP)+,D0-D1/A0-A3 ; Restore registers
MOVE (SP)+,SR ; restore interrupts
UNLK A6
RTS ; and return
; This is the interrupt routine for receiving through
; the printer port. It places the counter value and the
; MIDI byte in a circular queue to be
; accessed later by the application.
; When the system gets this far, A0 contains the
; SCC base read Ctl address
; and A1 contains the SCC base write Ctl address
; for this channel. The data addresses are offset by 4
; from the control addresses.
; D0-D3/A0-A3 are already preserved, so they may
; be used freely.
RxIntHandB
ORI #$0300,SR ; disable interrupts
@3 MOVE #4,D0 ; get data offset
CLR.L D1 ; prepare for data
MOVE.L (SP),(SP) ; Delay
MOVE.B 0(A0,D0),D1 ; read data from SCC
MOVE.L (SP),(SP) ; Delay
LEA RxQueueB,A2 ; point to queue
LEA RxByteInB,A3 ; get the address
MOVE (A3),D0 ; get offset to next cell
LEA Counter,A3 ; get the address
MOVE.L (A3),D2 ; put counter value in D2
LSL.L #8,D2 ; shift counter one byte
ADD.L D2,D1 ; combine counter and data
MOVE.L D1,0(A2,D0) ; put longword in queue
LEA RxQEmptyB,A3 ; get the address
MOVE #0,(A3) ; reset queue empty flag
ADDQ #4,D0 ; update index
CMP #$400,D0
BNE @1
MOVE #0,D0
@1 LEA RxByteInB,A3 ; get the address
MOVE D0,(A3)
@2 BTST.B #0,(A0) ; is there more data?
BNE @3 ; do it again if there is
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; This is the interrupt routine for transmitting a byte
; through the printer port. It checks to see if there
; is any data to send, and if there is it sends it to
; the SCC. If there isn't it resets the TBE interrupt
; in the SCC and exits.
; When the system gets this far, A0 contains the SCC
; base read Ctl address and A1 contains the SCC base
; write Ctl address for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so
; they may be used freely.
TxIntHandB
ORI #$0300,SR ; disable interrupts
LEA TxQEmptyB,A3 ; get the address
TST.B (A3) ; Is queue empty?
BEQ @1 ; if not branch
MOVE.B #$28,(A1) ; if so, reset TBE interrupt
MOVE.L (SP),(SP) ; Delay
BRA TxIExitB ; and exit
@1 LEA TxByteOutB,A3 ; get the address
MOVE (A3),D0 ; get index to next data byte
LEA TxQueueB,A2 ; point to queue
MOVE #4,D1 ; get data offset
MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
ADDQ #1,D0 ; update index
CMP #$100,D0
BNE @2
MOVE #0,D0
@2 LEA TxByteOutB,A3 ; get the address
MOVE D0,(A3)
LEA TxByteInB,A3 ; get the address
MOVE (A3),D1
CMP D0,D1 ; is TxQueue empty?
BNE TxIExitB ; if not exit
LEA TxQEmptyB,A3 ; get the address
MOVE #$FFFF,(A3) ; if empty set flag
TxIExitB
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; PROCEDURE ResetSCCB;
; If you called InitSCCB at the beginning of your
; application this
; routine must be called when the application
; quits or the system will
; crash due to the interrupt handling pointers
; becoming invalid.
ResetSCCB
MOVEM.L D0/A0-A1,-(SP) ; save registers
MOVE SR,-(SP) ; Save interrupts
ORI #$0300,SR ; Disable interrupts
MOVE.L SCCWr,A0 ; Get base Write address
ADD #bCtl,A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%01000000,(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
BSR ResetSCCChan ; branch to common reset routine
MOVE.L #Lvl2DT,A0 ; get dispatch table pointer
MOVE #RxIntOffsetB,D0 ; get offset to Rx vector
LEA PRxIntHandB,A1 ; point to previous vector storage
MOVE.L (A1),0(A0,D0) ; restore previous int vector
MOVE #TxIntOffsetB,D0 ; get offset to Tx vector
LEA PTxIntHandB,A1 ; set Rx vector
MOVE.L (A1),0(A0,D0) ; restore previous int vector
MOVE (SP)+,SR ; Restore interrupts
MOVEM.L (SP)+,D0/A0-A1 ; restore registers
RTS ; and return
TxQueueB DCB.B $100,0 ; this is the queue
TxQEmptyB DC 0 ; the queue empty flag
TxByteInB DC 0 ; index to next cell in
TxByteOutB DC 0 ; index to next cell out
RxQueueB DCB.B $400,0 ; this is the queue
RxQEmptyB DC 0 ; the empty queue flag
RxByteInB DC 0 ; index to next cell in
RxByteOutB DC 0 ; index to next cell out
PRxIntHandB DC 0 ; Previous interrupt vector
PTxIntHandB DC 0 ; Previous interrupt vector
; This is the space for a special condition interrupt
; routine. All I do here is reset the error flag in the SCC
; and return. When the system gets this far, A0 contains
; the SCC base read Ctl address
; and A1 contains the SCC base write Ctl address
; for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so
; they may be used freely.
StubA
ORI #$0300,SR ; Disable interrupts
MOVE.B #%00110000,(A1) ; Reset Error
MOVE.L (SP),(SP) ; Delay
ANDI #$F8FF,SR ; Restore interrupts
RTS
; This is the space for a special condition interrupt
; routine. All I do here is reset the error flag in the SCC
; and return. When the system gets this far, A0 contains
; the SCC base read Ctl address
; and A1 contains the SCC base write Ctl address
; for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so
; they may be used freely.
StubB
ORI #$0300,SR ; Disable interrupts
MOVE.B #%00110000,(A1) ; Reset Error
MOVE.L (SP),(SP) ; Delay
ANDI #$F8FF,SR ; Restore interrupts
RTS
; These are the routines for the counter you can use for
; time-stamping the incoming MIDI data. This is useful
; for writing sequencer type applications.
; The time-stamping is done on an iterrupt level,
; is extremely accurate,
; and uses the VIA timer #1. This means that you can't
; use any of the Sound Manager routines because they use
; timer #1 too. If you want to create a metronome click
; you have to write your own code that accesses
; the sound hardware directly without using timer #1.
; InitTimer and LoadTimer expect a word on the stack
; to load the timer.
; To increment the counter every millisecond, load the
; timer with decimal 782. If you aren't going to use
; time-stamping you can ignore these routines.
; PROCEDURE InitTimer ( TimrValue : integer);
; Only call InitTimer once at the beginning
; of your application 1 millisecond is decimal 782.
InitTimer
LINK A6,#0 ; set frame pointer
MOVEM.L D0/A0-A1,-(SP)
MOVE.L #Lvl1DT,A0 ; Point to level 1 dispatch table
LEA PrevIVC,A1 ; Point to interrupt vector storage
MOVE.L 24(A0),(A1) ; save previous interrupt vector
LEA CounterIntHand,A1 ; point to new interrupt handler
MOVE.L A1,24(A0) ; put it in the dispatch table
MOVE.L VIA,A1 ; point to the 6522 chip
ORI.B #$40,vACR(A1) ; set the timer to freerun mode
MOVE.B #$C0,vIER(A1) ; Enable timer interrupts
MOVE 8(A6),D0 ; Get timer value
MOVE.B D0,vT1L(A1) ; set timer lo byte
LSR #8, D0 ; shift to hi byte
MOVE.B D0,vT1CH(A1) ; set timer hi byte
MOVEM.L (SP)+,D0/A0-A1
UNLK A6
MOVE.L (SP)+,A0 ; save return address
ADDQ #2,SP ; move past timer value
MOVE.L A0,-(SP) ; replace return address
RTS
; PROCEDURE LoadTimer (TimrValue : integer);
; Call LoadTimer whenever you want to change the timer value.
; 1 millisecond is decimal 782.
LoadTimer
LINK A6,#0 ; set frame pointer
MOVEM.L D0/A0-A1,-(SP)
MOVE.L VIA,A1 ; point to the 6522 chip
MOVE 8(A6),D0 ; Get timer value
MOVE.B D0,vT1L(A1) ; set timer lo byte
LSR #8,D0 ; shift to hi byte
MOVE.B D0,vT1CH(A1) ; set timer hi byte
MOVEM.L (SP)+,D0/A0-A1
UNLK A6
MOVE.L (SP)+,A0 ; save return address
ADDQ #2,SP ; move past timer value
MOVE.L A0,-(SP) ; replace return address
RTS
; PROCEDURE StartCounter;
; StartCounter sets the counter value to 1
StartCounter
LEA Counter,A0 ; point to the counter
MOVE.L #1,(A0) ; set it to 1
RTS
; FUNCTION GetCounter : LongInt;
; GetCounter returns a longword that is the value
; of the counter
GetCounter
MOVE.L A0,-(SP)
LEA Counter,A0 ; point to the counter
MOVE.L (A0),8(SP) ; return it as function result
MOVE.L (SP)+,A0
RTS
; PROCEDURE QuitTimer;
; Call QuitTimer when your application is done or the system will crash.
QuitTimer
MOVEM.L A0-A1,-(SP)
MOVE.L VIA,A1 ; Disable 6522 interrupts
MOVE.B #$40,vIER(A1)
LEA PrevIVC,A1 ; Restore previous interrupt vector
MOVE.L #Lvl1DT,A0
MOVE.L (A1),24(A0)
MOVEM.L (SP)+,A0-A1
RTS
; This is the interrupt handler routine for the counter.
; When the system gets this far A1 contains the base
; address of the VIA.
; It also preserves D0-D3/A0-A3.
CounterIntHand
LEA Counter,A0 ; point to the counter
ADDQ.L #1,(A0) ; Increment it
MOVE.B vT1C(A1),D0 ; Clear interrupt flag on 6522
RTS
Counter DC.L 1 ; The counter
PrevIVC DC.L 0 ; Previous interrupt vector
END